package com.itranswarp.weibo.api;

import java.io.IOException;
import java.io.Reader;
import java.util.LinkedList;
import java.util.List;

import com.itranswarp.weibo.repackage.com.google.gson.stream.JsonReader;
import com.itranswarp.weibo.repackage.com.google.gson.stream.JsonToken;
import static com.itranswarp.weibo.repackage.com.google.gson.stream.JsonToken.*;

class JsonParser {

	@SuppressWarnings("unchecked")
	<T> T parseJson(Reader r) throws IOException {
		JsonReader reader = new JsonReader(r);
		JsonToken token = reader.peek();
		switch (token) {
		case NULL:
			parseNull(reader);
			assertEnd(reader);
			return null;
		case STRING:
			String v = parseString(reader);
			assertEnd(reader);
			return (T) v;
		case NUMBER:
			Number n = parseNumber(reader);
			assertEnd(reader);
			return (T) n;
		case BOOLEAN:
			Boolean b = parseBoolean(reader);
			assertEnd(reader);
			return (T) b;
		case BEGIN_OBJECT:
			return (T) parseObject(reader);
		case BEGIN_ARRAY:
			return (T) parseArray(reader);
		}
		throw new JsonException("Unknown token.");
	}

	// always return null:
	Object parseNull(JsonReader reader) throws IOException {
		reader.nextNull();
		return null;
	}

	Boolean parseBoolean(JsonReader reader) throws IOException {
		return reader.nextBoolean();
	}

	String parseString(JsonReader reader) throws IOException {
		return reader.nextString();
	}

	Number parseNumber(JsonReader reader) throws IOException {
		String n = reader.nextString();
		if (n.indexOf('.')==(-1) && n.indexOf('e')==(-1))
			return new Long(n);
		return new Double(n);
	}

	JsonObject parseObject(JsonReader reader) throws IOException {
		JsonObject jo = new JsonObject();
		String key = null;
		reader.beginObject();
		boolean shouldContinue = true;
		while (shouldContinue) {
			JsonToken token = reader.peek();
			switch (token) {
			case BEGIN_OBJECT:
				jo.set(assertNotNull(key), parseObject(reader));
				key = null;
				break;
			case END_OBJECT:
				assertNull(key);
				reader.endObject();
				shouldContinue = false;
				break;
			case BEGIN_ARRAY:
				jo.set(assertNotNull(key), parseArray(reader));
				key = null;
				break;
			case END_ARRAY:
				throw new JsonException("Unexpected token: ]");
			case NAME:
				key = reader.nextName();
				break;
			case STRING:
				jo.set(assertNotNull(key), parseString(reader));
				key = null;
				break;
			case NUMBER:
				jo.set(assertNotNull(key), parseNumber(reader));
				key = null;
				break;
			case BOOLEAN:
				jo.set(assertNotNull(key), parseBoolean(reader));
				key = null;
				break;
			case NULL:
				jo.set(assertNotNull(key), parseNull(reader));
				key = null;
				break;
			}
		}
		return jo;
	}

	Object[] parseArray(JsonReader reader) throws IOException {
		List<Object> list = new LinkedList<Object>();
		reader.beginArray();
		boolean shouldContinue = true;
		while (shouldContinue) {
			JsonToken token = reader.peek();
			switch (token) {
			case BEGIN_OBJECT:
				list.add(parseObject(reader));
				break;
			case END_OBJECT:
				throw new JsonException("Unexpected token: }");
			case BEGIN_ARRAY:
				list.add(parseArray(reader));
				break;
			case END_ARRAY:
				reader.endArray();
				shouldContinue = false;
				break;
			case NAME:
				throw new JsonException("Unexpected token: name");
			case STRING:
				list.add(parseString(reader));
				break;
			case NUMBER:
				list.add(parseNumber(reader));
				break;
			case BOOLEAN:
				list.add(parseBoolean(reader));
				break;
			case NULL:
				list.add(parseNull(reader));
				break;
			}
		}
		return list.toArray();
	}

	void assertEnd(JsonReader reader) throws IOException {
		if (reader.peek() != END_DOCUMENT)
			throw new JsonException("Expected end but unexpected token.");
	}

	void assertNull(String s) {
		if (s != null)
			throw new JsonException("String is not null.");
	}

	String assertNotNull(String s) {
		if (s==null)
			throw new JsonException("String is null.");
		return s;
	}

}
